#include "sdUniverse.h"
#include <iostream>

using namespace std;
sdUniverse* sdUniverse::this_;
std::map<cpShape*, sdObject*> sdUniverse::objects_;
std::set<sdObject*> sdUniverse::doomed;
std::set<sdTimed*> sdUniverse::timed_;
std::set<sdBad*> sdUniverse::bad_;
bool sdUniverse::killPlayer_;
int sdUniverse::kills;

int sdUniverse::sdCollisionBW(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    
	
	sdTimed* bullet = (sdTimed*) objects_[a];
	sdBadChunk* chunk = (sdBadChunk*) objects_[a];

	std::set<sdTimed*> chunks;

    switch (a->collision_type) {
	case 2: //Bullet
		doomed.insert(bullet);
		timed_.erase(bullet);
		
		
		bullet->getChunks(&chunks);
    
		for(std::set<sdTimed*>::iterator it = chunks.begin(); it != chunks.end(); ++it) {
			this_->insert(*(*it));
			timed_.insert(*it);
		}
		break;

	case 6: //BadChunk
		doomed.insert(chunk);
		timed_.erase(chunk);

		chunk->getChunks(&chunks);
    
		for(std::set<sdTimed*>::iterator it = chunks.begin(); it != chunks.end(); ++it) {
			this_->insert(*(*it));
			timed_.insert(*it);
		}
		break;
	}
    return 1;
}

int sdUniverse::sdCollisionSW(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    return 1;
}

int sdUniverse::sdCollisionBS(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    return 1;
}

int sdUniverse::sdCollisionBE(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    sdTimed* bullet = (sdTimed*) objects_[a];

	sdBad* bad = (sdBad*) objects_[b];
    bad->takeDamage(1);

	if(bad->dead() && !bad->killed()) {
		bad->kill();
        doomed.insert(bad);
        bad_.erase(bad);
        
        std::set<sdBadChunk*> chunks;
        bad->getChunks(&chunks);
        
        for(std::set<sdBadChunk*>::iterator it = chunks.begin(); it != chunks.end(); ++it) {
            this_->insert(*(*it));
            timed_.insert(*it);
        }
        
        ++kills;
    }

	doomed.insert(bullet);
    timed_.erase(bullet);
    
    std::set<sdTimed*> bChunks;
    bullet->getChunks(&bChunks);
    
    for(std::set<sdTimed*>::iterator jt = bChunks.begin(); jt != bChunks.end(); ++jt) {
        this_->insert(*(*jt));
        timed_.insert(*jt);
    }
    
    return 1;
}

int sdUniverse::sdCollisionSE(cpShape *a, cpShape *b, cpContact *contacts, int numContacts, cpFloat normal_coef, void *data) {
    sdShip* ship = (sdShip*) objects_[a];
    ship->takeDamage(1);
    
    sdBad* bad = (sdBad*) objects_[b];
    if(!bad->killed()) {
		bad->kill();
		doomed.insert(bad);
		bad_.erase(bad);
        
		std::set<sdBadChunk*> chunks;
		bad->getChunks(&chunks);

		for(std::set<sdBadChunk*>::iterator it = chunks.begin(); it != chunks.end(); ++it) {
		    this_->insert(*(*it));
			timed_.insert(*it);
		}
    }
        
    if(ship->dead()) {
        std::set<sdTimed*> shipChunks;
        ship->getChunks(&shipChunks);
        
        for(std::set<sdTimed*>::iterator it = shipChunks.begin(); it != shipChunks.end(); ++it) {
            this_->insert(*(*it));
            timed_.insert(*it);
        }
        
        killPlayer_ = true;
        
        
        //doomed.insert(ship);
    }
        
    return 1;
}

void sdUniverse::insert(sdObject& o) {
    /*std::cout << "Insert - Adding body[" << &o << "]" << std::endl;
    std::cout << "Insert - Space[" << space_ << "]" << std::endl;
    std::cout.flush();*/
    cpSpaceAddBody(space_, &(o.getBody())); //SEGFAULTS

    /*std::cout << "Insert - starting loop" << std::endl;
    std::cout.flush();*/
    for(std::vector<cpShape*>::iterator it = o.getShapes().begin(); it != o.getShapes().end(); ++it) {
        /*std::cout << "Insert - Adding shape[" << *it << "]" << std::endl;
        std::cout.flush();*/
        cpSpaceAddShape(space_, *it);
        objects_[*it] = &o;
        //std::cout << "Creating  Key[" << *it << "] Value[" << objects_[*it] << "]" << std::endl;
    }
}

void sdUniverse::init() {
    statics_ = cpBodyNew(INFINITY, INFINITY);

    int width = input_.getWidth();
    int height = input_.getHeight();

    insert(player_.getShip());
    

    cpResetShapeIdCounter();

    cpSpaceResizeStaticHash(space_, 700.0, 40);
    space_->gravity = cpv(0, 0);

    cpSpaceResizeActiveHash(space_, 5.0, 6000);

    //cpSpaceAddCollisionPairFunc(space_, 0, 0, collisionCb, 0);
    
    cpSpaceAddCollisionPairFunc(space_, 2, 0, sdUniverse::sdCollisionBW, 0);
	cpSpaceAddCollisionPairFunc(space_, 6, 0, sdUniverse::sdCollisionBW, 0);
    cpSpaceAddCollisionPairFunc(space_, 1, 0, sdUniverse::sdCollisionSW, 0);
    cpSpaceAddCollisionPairFunc(space_, 2, 1, sdUniverse::sdCollisionBS, 0);
    cpSpaceAddCollisionPairFunc(space_, 2, 3, sdUniverse::sdCollisionBE, 0);
	cpSpaceAddCollisionPairFunc(space_, 6, 3, sdUniverse::sdCollisionBE, 0);
    cpSpaceAddCollisionPairFunc(space_, 1, 3, sdUniverse::sdCollisionSE, 0);


    //TODO The walls need to be redone using an offset rather than global coords
    
    sdObject left(statics_);
    sdObject right(statics_);
    sdObject top(statics_);
    sdObject bottom(statics_);
   
    int walls = 4;
    int wallWidth = 40;
    int vertWallFromSides = -wallWidth;
    int horzWallFromSides = -wallWidth;

    //TODO: THE FOLLOWING MUST BE OBJECTIZED

    cpVect vLeft[] = {
        cpv(-((width / 2) - vertWallFromSides),             -((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides),              ((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides - wallWidth),  ((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides - wallWidth), -((height / 2) - horzWallFromSides)),
    };

    cpVect vRight[] = {
        cpv(((width / 2) - vertWallFromSides - wallWidth), -((height / 2) - horzWallFromSides)),
        cpv(((width / 2) - vertWallFromSides - wallWidth),  ((height / 2) - horzWallFromSides)),
        cpv(((width / 2) - vertWallFromSides),              ((height / 2) - horzWallFromSides)),
        cpv(((width / 2) - vertWallFromSides),             -((height / 2) - horzWallFromSides)),
    };

    cpVect vTop[] = {
        cpv(-((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides - wallWidth)),
        cpv(-((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides)),
        cpv( ((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides)),
        cpv( ((width / 2) - vertWallFromSides), ((height / 2) - horzWallFromSides - wallWidth)),
    };

    cpVect vBottom[] = {
        cpv(-((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides)),
        cpv(-((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides - wallWidth)),
        cpv( ((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides - wallWidth)),
        cpv( ((width / 2) - vertWallFromSides), -((height / 2) - horzWallFromSides)),
    };
    
    left.addPolyShape(walls, vLeft, cpvzero, 0);
    right.addPolyShape(walls, vRight, cpvzero, 0);
    top.addPolyShape(walls, vTop, cpvzero, 0);
    bottom.addPolyShape(walls, vBottom, cpvzero, 0);

    insert(left);
    insert(right);
    insert(top);
    insert(bottom);
}

void sdUniverse::updateObjects(cpFloat ticks) {
	std::set<sdTimed*>::iterator deleteMe;
	bool deletePrev = false;
	
	for(std::set<sdTimed*>::iterator ui = timed_.begin(); ui != timed_.end(); ++ui) {
		if(deletePrev) {
			timed_.erase(deleteMe);
			deletePrev = false;
		}
		(*ui)->update(ticks);
        
        if((*ui)->dead()) {
            doomed.insert(*ui);
			for(std::vector<cpShape*>::iterator jt = (*ui)->getShapes().begin(); jt != (*ui)->getShapes().end(); ++jt) {
				objects_.erase(*jt);
            }
			          
			if(ui != timed_.end()) {
				(*ui)->destroy(space_);
				deleteMe = ui;
				deletePrev = true;
			}

        }
    }
	if(deletePrev) {
		timed_.erase(deleteMe);
		deletePrev = false;
	}
}

void sdUniverse::kill() {
	//std::cout << "killA" << std::endl;
	//std::cout.flush();

    for(set<sdObject*>::iterator it = doomed.begin(); it != doomed.end(); ++it) {
        //std::cout << "loop [" << *it << "]" << std::endl;
		//std::cout.flush();
		
		for(vector<cpShape*>::iterator jt = (*it)->getShapes().begin(); jt != (*it)->getShapes().end(); ++jt) {
            //std::cout << "iloop [" << *jt << "]...";
			//std::cout.flush();
			objects_.erase(*jt);
			//std::cout << "!";
			//std::cout.flush();
        }
		//std::cout << "Destroying loop [" << *it << "]" << std::endl;
		//std::cout.flush();
        (*it)->destroy(space_);
        
        delete (*it);
    }
}

void sdUniverse::moveBads() {
    for(set<sdBad*>::iterator it = bad_.begin(); it != bad_.end(); ++it) {
        (*it)->follow();
    }
}

void sdUniverse::addBad(int hp, int hpMax, cpFloat velocity, cpFloat turn, cpVect pos) {
    sdBad* bad = new sdBad(hp, hpMax, velocity, turn, pos);
    bad->setTarget(&(getPlayer().getShip()));
    insert(*bad);
    bad_.insert(bad);
}

void sdUniverse::update() {
    cpFloat dt = 0.5/60.0;
    cpSpaceStep(space_, dt);
    
	//std::cout << "a";
	//std::cout.flush();

    //Call kill() to kill all the doomed objects
    kill();

	//std::cout << "b";
	//std::cout.flush();
    
    doomed.clear();
    
	//std::cout << "c";
	//std::cout.flush();

    updateObjects(dt);

	//std::cout << "d";
	//std::cout.flush();

	if(getPlayer().alive()) {
        cpVect mouseLoc = getInputClass().getMouse();
		
		getPlayer().update(dt);

        getPlayer().turn(mouseLoc);

        cpVect direction = getInputClass().getDirection();

        getPlayer().move(direction);

		if(getInputClass()['c'] || getInputClass().leftButtonDown()) {
			std::set<sdTimed*> bullets;
            if(getPlayer().shoot(0, &bullets)) {
				for(std::set<sdTimed*>::iterator bt = bullets.begin(); bt != bullets.end(); ++bt) {
					this_->insert(*(*bt));
					timed_.insert(*bt);
				}
            }/* else {
                //delete &bullets;
            }*/
        }

		if(getInputClass()['v'] || getInputClass().rightButtonDown()) {
			std::set<sdTimed*> bullets;
            if(getPlayer().shoot(1, &bullets)) {
				for(std::set<sdTimed*>::iterator bt = bullets.begin(); bt != bullets.end(); ++bt) {
					this_->insert(*(*bt));
					timed_.insert(*bt);
				}
            }/* else {
                //delete &bullets;
            }*/
        }
        
        if(getInputClass()['x'] || getInputClass().rightButtonDown()) {
			std::set<sdTimed*> bullets;
            if(getPlayer().shoot(2, &bullets)) {
				for(std::set<sdTimed*>::iterator bt = bullets.begin(); bt != bullets.end(); ++bt) {
					this_->insert(*(*bt));
					timed_.insert(*bt);
				}
            }/* else {
                //delete &bullets;
            }*/
        }

		if(timeToSpawn_ < 0) {
		    //std::cout << "[" << nextSpawnTime_ << "][" << deltaSpawn_ << "]" << std::endl;
			if(nextSpawnTime_ > 0.2) {
                nextSpawnTime_ -= deltaSpawn_;
            }
            timeToSpawn_ = nextSpawnTime_;
            addBad(2, 2, badVel += 500.0, badTurn += 0.5, randomLoc());
        } else {
            timeToSpawn_ -= dt;
        }
        
        moveBads();
    }
    
    if(killPlayer_) {
        for(vector<cpShape*>::iterator jt = getPlayer().getShip().getShapes().begin(); jt != getPlayer().getShip().getShapes().end(); ++jt) {
            objects_.erase(*jt);
        }
        getPlayer().getShip().destroy(space_);
        killPlayer_ = false;
        
        sdBad* bad;
        std::set<sdBad*>::iterator dead;
        bool kill = false;
        
        for(std::set<sdBad*>::iterator it = bad_.begin(); it != bad_.end(); ++it) {
            bad = *it;
            bad->kill();
            doomed.insert(bad);
            
            std::set<sdBadChunk*> chunks;
            bad->getChunks(&chunks);
            for(std::set<sdBadChunk*>::iterator tt = chunks.begin(); tt != chunks.end(); ++tt) {
                this_->insert(*(*tt));
                timed_.insert(*tt);
            }
            
            kill = true;
            
        }
        
        quitting_ = true;
        std::cout << "You survived for " << kills << " kills" << std::endl;
    }
    
    if(quitting_) {
        timeToQuit_ -= dt;
        if(timeToQuit_ < 0) {
            exit(0);
        }
    }
	//std::cout << "e";
	//std::cout.flush();
}

